package com.hm.common.plugin;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.xml.bind.PropertyException;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hm.common.util.ReflectHelper;
import com.hm.common.util.SpringUtil;
import com.hm.common.util.StackTraceUtil;
import com.hm.oper_monitor.bean.ResourcesManager;

public abstract class PaginationBasePlugin implements Interceptor {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	private static String pageSqlId;

	public Object intercept(Invocation invocation) throws Throwable {
		if (invocation.getTarget() instanceof RoutingStatementHandler) {
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation
					.getTarget();
			BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper
					.getValueByFieldName(statementHandler, "delegate");
			MappedStatement mappedStatement = (MappedStatement) ReflectHelper
					.getValueByFieldName(delegate, "mappedStatement");
			BoundSql boundSql = delegate.getBoundSql();

			RowBounds rowBounds = (RowBounds) ReflectHelper
					.getValueByFieldName(delegate, "rowBounds");

			if (mappedStatement.getId().matches(pageSqlId)) {
				Page page = SpringUtil.getBean(ResourcesManager.class).getPage();
				if (rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET
						&& rowBounds.getLimit() == RowBounds.NO_ROW_LIMIT) {
					ReflectHelper.setValueByFieldName(rowBounds, "offset",
							page.getDisplayStart());
					ReflectHelper.setValueByFieldName(rowBounds, "limit",
							page.getDisplayLength());
				}

				// 分页SQL<select>中parameterType属性对应的实体参数，即Mapper接口中执行分页方法的参数,该参数不得为空
				// Object parameterObject = boundSql.getParameterObject();
				// if (parameterObject == null) {
				// throw new NullPointerException("parameterObject尚未实例化！");
				// } else {
				Connection connection = (Connection) invocation.getArgs()[0];

				page.setTotalCount(getSelectTotalCount(connection, boundSql,
						mappedStatement));
				// 将分页sql语句反射回BoundSql.
				ReflectHelper.setValueByFieldName(boundSql, "sql",
						paginationSql(boundSql.getSql(), rowBounds));
				// 将rowBounds反射为无限制，否则mybatis会根据rowBounds把select的结果列表再做一次过滤
				ReflectHelper.setValueByFieldName(rowBounds, "offset",
						RowBounds.NO_ROW_OFFSET);
				ReflectHelper.setValueByFieldName(rowBounds, "limit",
						RowBounds.NO_ROW_LIMIT);
				// }
			}
		}

		return invocation.proceed();
	}

	private int getSelectTotalCount(Connection connection, BoundSql boundSql,
			MappedStatement mappedStatement) throws SQLException,
			SecurityException, IllegalArgumentException, NoSuchFieldException,
			IllegalAccessException {
		ResultSet rs = null;
		PreparedStatement countStmt = null;

		try {
			String countSql = "select count(1) from (" + boundSql.getSql()
					+ ") alias"; // 记录统计
			countStmt = connection.prepareStatement(countSql);
			BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(),
					countSql, boundSql.getParameterMappings(),
					boundSql.getParameterObject());
			@SuppressWarnings("unchecked")
			Map<String, Object> additionalParameters = (Map<String, Object>) ReflectHelper
					.getValueByFieldName(boundSql, "additionalParameters");
			for (Entry<String, Object> entry : additionalParameters.entrySet()) {
				countBS.setAdditionalParameter(entry.getKey(), entry.getValue());
			}

			setParameters(countStmt, mappedStatement, countBS,
					boundSql.getParameterObject());
			rs = countStmt.executeQuery();

			if (rs.next()) {
				return rs.getInt(1);
			}

			return 0;

		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (Exception ex) {
				}
			}

			if (countStmt != null) {
				try {
					countStmt.close();
				} catch (Exception ex) {
				}
			}
		}
	}

	/**
	 * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.
	 * DefaultParameterHandler
	 * 
	 * @param ps
	 * @param mappedStatement
	 * @param boundSql
	 * @param parameterObject
	 * @throws SQLException
	 */
	@SuppressWarnings("unchecked")
	private void setParameters(PreparedStatement ps,
			MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters")
				.object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql
				.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration
					.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null
					: configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry
							.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName
							.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value)
									.getValue(
											propertyName.substring(prop
													.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject
								.getValue(propertyName);
					}
					@SuppressWarnings("rawtypes")
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException(
								"There was no TypeHandler found for parameter "
										+ propertyName + " of statement "
										+ mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value,
							parameterMapping.getJdbcType());
				}
			}
		}
	}

	private RowBounds checkAnnotation(MappedStatement mappedStatement) {
		String statementId = mappedStatement.getId();
		String namespace = getNamespace(statementId);
		String methodName = getmethodName(statementId);

		Class<?> type = null;
		try {
			type = Resources.classForName(namespace);

			if (type.isInterface()) {
				Method[] methods = type.getMethods();
				for (Method method : methods) {
					if (StringUtils.equals(methodName, method.getName())
							&& method.isAnnotationPresent(ListPage.class)) {
						Page page = SpringUtil.getBean(ResourcesManager.class).getPage();
						return new RowBounds(page.getDisplayStart(),
								page.getDisplayLength());
					}
				}
			}
		} catch (Exception e) {
			// do nothing
		}

		return new RowBounds();
	}

	private String getNamespace(String statementId) {
		int idx = statementId.lastIndexOf('.');
		if (idx != -1) {
			return statementId.substring(0, idx);
		}
		return null;
	}

	private String getmethodName(String statementId) {
		int idx = statementId.lastIndexOf('.');
		if (idx != -1) {
			return statementId.substring(idx + 1);
		}
		return null;
	}

	protected abstract String paginationSql(String sql, RowBounds rowBounds); // 子类实现

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {
		pageSqlId = properties.getProperty("pageSqlId");
		if (StringUtils.isEmpty(pageSqlId)) {
			try {
				throw new PropertyException("pageSqlId property is not found!");
			} catch (PropertyException e) {
				logger.error(StackTraceUtil.getStackTrace(e));
			}
		}
	}

	public static String getPageSqlId() {
		return pageSqlId;
	}

	public static void setPageSqlId(String pageSqlId) {
		PaginationBasePlugin.pageSqlId = pageSqlId;
	}
}
